import java.io.BufferedWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.BooleanLiteral;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.ForStatement;
import org.eclipse.jdt.core.dom.IfStatement;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jdt.core.dom.WhileStatement;
import org.eclipse.jdt.internal.compiler.ast.Block;


public class CFGNode {
	public static int CFG_ENTRY = 0;
	public static int CFG_EXIT = 1;
	private boolean checked = false;
	private ArrayList<CFGNode> parents;
	private ArrayList<CFGNode> children;
	private LinkedList<Statement> statements;
	private CFGNode next;
	private int lastValue = -1;
	
	
	public CFGNode(int type) {
		statements = new LinkedList<Statement>();
		if (type == CFG_ENTRY){
			children = new ArrayList<CFGNode>();
		}
		if (type == CFG_EXIT){
			parents = new ArrayList<CFGNode>();
		}
	}
	
	public CFGNode() {
		statements = new LinkedList<Statement>();
		parents = new ArrayList<CFGNode>();
		children = new ArrayList<CFGNode>();
	}
	
	public void addParent(CFGNode parent) {
		if (!parents.contains(parent))
			parents.add(parent);
	}

	public void addChild(CFGNode node){
		if(!children.contains(node))
		{
			children.add(node);
			node.addParent(this);
		}
	}
	
	public void addStatement(Statement st) {
		statements.add(st);
	}
	
	public String toString(){
		String temp = "";
		for (Statement st : statements) {
			temp += st.toString();
		}
		return temp;
	}
	
	public boolean hasBreak() {
		for (Statement s : statements) {
			if (s.getNodeType() == ASTNode.BREAK_STATEMENT)
				return true;
		}
		return false;
	}
	
	public boolean hasReturn() {
		for (Statement s : statements) {
			if (s.getNodeType() == ASTNode.RETURN_STATEMENT)
				return true;
		}
		return false;
	}
	
	public int getLastValue(String varName) {
		int val = -1;
		for (int i = 0; i < statements.size(); i++) {
			Statement s = statements.get(i);
			if(s.getNodeType() == ASTNode.EXPRESSION_STATEMENT){
				ExpressionStatement es = (ExpressionStatement) s;
				if(es.getExpression().getNodeType() == Expression.ASSIGNMENT){
					Assignment as = (Assignment) es.getExpression();
					if(as.getLeftHandSide().toString().trim().equals(varName)){
						if(as.getRightHandSide().getNodeType() == Expression.BOOLEAN_LITERAL){
							boolean value = ((BooleanLiteral)as.getRightHandSide()).booleanValue();
							if(value) 
								val = 1;
							else 
								val = 0;
						}
						else
							System.err.println("TODO");
					}
				}
			}
		}
		return val;
	}
	
	public boolean isDefinition(String varName) {
		for (int i = 0; i < statements.size(); i++) {
			Statement s = statements.get(i);
			if(s.getNodeType() == ASTNode.VARIABLE_DECLARATION_STATEMENT){
				VariableDeclarationStatement vda = (VariableDeclarationStatement) s;
				List<VariableDeclarationFragment> list = vda.fragments();
				for (VariableDeclarationFragment frag : list ) {
					if(frag.getName().toString().equals(varName));
						return true;
				}
			}
		}
		return false;
	}
	
	public boolean includesVariable(String varName) {
		for (int i = 0; i < statements.size(); i++) {
			Statement s = statements.get(i);
			if(s.getNodeType() == ASTNode.EXPRESSION_STATEMENT){
				ExpressionStatement es = (ExpressionStatement) s;
				if(es.getExpression().getNodeType() == Expression.ASSIGNMENT){
					Assignment as = (Assignment) es.getExpression();
					if(as.getLeftHandSide().toString().trim().equals(varName)){
						return true;
					}
				}
			}
		}
		return false;
	}
	
	public void changeToRet(int lastVal, String varName, CFG cfg) {
		for (int i = 0; i < statements.size(); i++) {
			Statement s = statements.get(i);
			if(s.getNodeType() == ASTNode.EXPRESSION_STATEMENT){
				ExpressionStatement es = (ExpressionStatement) s;
				if(es.getExpression().getNodeType() == Expression.ASSIGNMENT){
					Assignment as = (Assignment) es.getExpression();
					if(as.getLeftHandSide().toString().trim().equals(varName)){
						if(as.getRightHandSide().getNodeType() == Expression.BOOLEAN_LITERAL){
							boolean value = ((BooleanLiteral)as.getRightHandSide()).booleanValue();
							if((value && lastVal==1)||(!value && lastVal ==0)) 
							{
								s = convertStatement(s,value,varName);
								int size = statements.size() - i;
								for(int j = 0; j < size; j++)
									statements.remove(i);
								statements.add(i,s);
								children.clear();
								children.add(cfg.getExit());
							}
							else 
								continue;
						}
						else
							System.err.println("TODO");
					}
				}
			}
		}
	}
	
	private Statement convertStatement(Statement s, boolean value, String varName) {
		AST ast = s.getAST();
		ReturnStatement rs = ast.newReturnStatement();
		Expression exp = ast.newBooleanLiteral(value);
		rs.setExpression(exp);
		rs.setFlags(s.getFlags());
		rs.setSourceRange(s.getStartPosition(), s.getLength());
		return rs;
	}
	
	public void print(BufferedWriter writer, String tabbing) throws IOException {
		for (Statement s : statements) {
			if (s.getNodeType() == ASTNode.IF_STATEMENT){
				IfStatement ifS = (IfStatement) s;
				writer.write(tabbing + "if (" + ifS.getExpression().toString() + "){");
				writer.newLine();
				CFGNode thenNode = this.children.get(0);
				thenNode.print(writer,tabbing.concat("\t"));
				if (children.get(1) != next){
					CFGNode elseNode = this.children.get(1);
					writer.write(tabbing + "} else {");
					writer.newLine();
					elseNode.print(writer,tabbing.concat("\t"));
				}
				writer.write(tabbing + "}");
				writer.newLine();
			}
			else if (s.getNodeType() == ASTNode.WHILE_STATEMENT){
				WhileStatement whileS = (WhileStatement) s;
				writer.write(tabbing + "while (" + whileS.getExpression().toString() + "){");
				writer.newLine();
				CFGNode loopNode = this.children.get(0);
				loopNode.print(writer,tabbing + "\t");
				writer.write(tabbing + "}");
				writer.newLine();
			}
			else if (s.getNodeType() == ASTNode.FOR_STATEMENT){
				ForStatement forS = (ForStatement) s;
				writer.write(tabbing + "for(" + forS.initializers().get(0).toString()+";");
				writer.write(forS.getExpression().toString() + ";" + forS.updaters().get(0).toString() + "){");
				writer.newLine();
				CFGNode loopNode = this.children.get(0);
				loopNode.print(writer,tabbing + "\t");
				writer.write(tabbing + "}");
				writer.newLine();
			}
			else{
				writer.write(tabbing + s.toString());
			}
		}
		if (next != null)
			next.print(writer,tabbing);
	}
	
	public boolean checked() {
		return checked ;
	}
	
	public void check() {
		checked = true;		
	}
	
	public int getLastVal() {
		return lastValue ;
	}
	
	public void setLastVal(int val) {
		lastValue = val;
	}
	
	public CFGNode getNext(){
		return next;
	}
	
	public void setNext(CFGNode n){
		next = n;
	}
	
	public ArrayList<CFGNode> getParents() {
		return parents;
	}
	
	public LinkedList<Statement> getStatements() {
		return statements;
	}
}
